##################################################################
# File : /lua/ui/menus/loginscreen.lua
# Description : DS main screen
# Author : Neruz
##################################################################

local Group = import('/lua/maui/group.lua').Group
local Bitmap = import('/lua/maui/bitmap.lua').Bitmap
local Text = import('/lua/maui/text.lua').Text
local TextFile = import('/lua/maui/text.lua')
local MultiLineText = import('/lua/maui/multilinetext.lua').MultiLineText
local Button = import('/lua/maui/button.lua').Button
local LayoutHelpers = import('/lua/maui/layouthelpers.lua')
local EffectHelpers = import('/lua/maui/effecthelpers.lua')
local Prefs = import('/lua/user/prefs.lua')
local UIUtil = import('/lua/ui/uiutil.lua')
local Edit = import('/lua/maui/edit.lua').Edit
local CryptEdit = import('/lua/maui/cryptedit.lua').CryptEdit
local ItemList = import('/lua/maui/itemlist.lua').ItemList
local RadioButtons = import('/lua/maui/radiobuttons.lua').RadioButtons
local Combo = import('/lua/ui/controls/combo.lua').Combo
local Window = import('/lua/ui/controls/window.lua').Window
local Network = import('/lua/ui/network/network.lua')	
local LobbySession = import('/lua/lobbysession.lua')

GUI = {
	loginWindow = false,
	announceWindow = false,
	miscwindow = false,
	bg = false,
	announceDescription = false,
	parent = false,
	registerWindow = false,
}

function CreateUI(parent)
    CreateDebugServers()
    
	GUI.parent = UIUtil.CreateScreenGroup(GetFrame(0), "loginScreen")
	GUI.bg = Bitmap(GUI.parent)
	GUI.bg:SetSolidColor('Gray')
	LayoutHelpers.FillParent(GUI.bg, GUI.parent)
	GUI.bg:SetAlpha(0.9)
	
	GUI.loginWindow = CreateLoginWindow(GUI.parent)
	GUI.announceWindow = CreateAnnounceWindow(GUI.parent)
	GUI.miscWindow = CreateMiscWindow(GUI.parent)
	
	# This client lives in the front-end and updates the game files and such
	_G.DataClient = CreateDataClient()
end

function CreateDataClient()
	local oneIndex = AddAnnouncement{time = 'Now', title = 'Connecting to server...', description = 'Connecting to server, please wait...'}
 	EffectHelpers.Pulse(GUI.announceWindow.list, 1.5, 0.8, 1)
 	
	local DataClient = Network.CreateDataClient()
	
	local OnConnectionFailed = function(client, obj, reason)
		RemoveAnnouncement(oneIndex)
		EffectHelpers.StopPulse(GUI.announceWindow.list)
		AddAnnouncement{time = 'Now', title = '[ERROR] Server unavailable', description = 'Could not connect to game server.\nreason: ' .. reason}
		EffectHelpers.FadeIn(GUI.announceWindow.ClientGroup, 1, 0, 1)
	end
	
	local OnConnectionToHostEstablished = function(client, obj, localId, localName, hostId)
		RemoveAnnouncement(oneIndex)
		oneIndex = AddAnnouncement{time = 'Now', title = 'Checking version...', description = 'Checking game version, please wait...'}
		client:SendData(hostId, {_type = "CheckVersion", version = __DSVERSION__})
	end
	
	local OnDataReceived = function(client, obj, data)
		local _type = data._type
		local senderId = data.SenderID
		local senderName = data.SenderName
		#LOG('Client ', repr(data))
		if _type == "VersionInfo" then
			RemoveAnnouncement(oneIndex)
			if data.version == __DSVERSION__ then
				AddAnnouncement{time = 'Now', title = 'Retreiving announcements...', description = 'Retreiving data, please wait...'}
				client:SendData(senderId, {_type = "RequestAnnouncements", id = -1})
			else
				EffectHelpers.StopPulse(GUI.announceWindow.list)
				AddAnnouncement{time = 'Now', title = '[ERROR] Version mismatch', description = 'Your Dark Skies version does not match the server\'s version'} # todo: more info
				EffectHelpers.FadeIn(GUI.announceWindow.ClientGroup, 1, 0, 1)
			end
		elseif _type == "Announcements" then
			RemoveAnnouncement(oneIndex)
			EffectHelpers.StopPulse(GUI.announceWindow.list)
			local announcements = data.announcements
			EffectHelpers.FadeIn(GUI.announceWindow.ClientGroup, 0.5, 0.5, 1)
			for k, ann in announcements do
				AddAnnouncement(ann)
			end
		end
	end
	
	AddMethodHook(DataClient, 'ConnectionToHostEstablished', OnConnectionToHostEstablished, DataClient)
	AddMethodHook(DataClient, 'ConnectionFailed', OnConnectionFailed, DataClient)
	AddMethodHook(DataClient, 'DataReceived', OnDataReceived, DataClient)
	
	return DataClient
end

function CreateDebugServers()
	WARN('*DEBUG: CREATING DEBUG SERVER')
	_G.DataServer = Network.CreateDataServer()
	_G.LoginServer = Network.CreateLoginServer()
#	_G.GameServer = Network.CreateGameServer()
end

function RemoveAnnouncement(index)
	local control = GUI.announceWindow.list
	control:DeleteItem(index)
	control.announcements[index] = nil
end

function AddAnnouncement(data)
	local title = data.title
	local time = data.time
	local description = data.description
	local control = GUI.announceWindow.list
	local index = control:GetItemCount()
	control:AddItem(' ' .. title)
	control.announcements[index] = data
	LOG('Added announcement, index', index, 'text', title)
	return index
end

function LaunchLobbySession()
	SetFrontEndData('DSLobbySession', true) # Needed to choose which gamemain module to load
	LobbySession.StartLobbySession()
	DataClient:Destroy()
end

function CreateLoginWindow(parent)
    loginWindow = Window(parent, 'Login Window ', nil, true, nil, nil, {true, true}, {true, true})
    loginWindow.Height:Set(180)
    loginWindow.Width:Set(300)
    LayoutHelpers.AtCenterIn(loginWindow, parent, -120, -190)
    loginWindow.Depth:Set(parent.Depth() + 1)
    
    local clientGroup = loginWindow.ClientGroup
    
	local accountNameText = UIUtil.CreateText(clientGroup, "Account Name: ", 15, UIUtil.bodyFont)
	LayoutHelpers.AtLeftIn(accountNameText, clientGroup, 15)
	LayoutHelpers.AtTopIn(accountNameText, clientGroup, 15)
	accountNameText.Depth:Set(clientGroup.Depth() + 1)
	
	loginWindow.accountName = UIUtil.CreateEditStd(clientGroup, 160, 20)
	LayoutHelpers.RightOf(loginWindow.accountName, accountNameText, 10)
	loginWindow.accountName:SetText(Prefs.GetFromCurrentProfile('remembered_name') or '')

	local passwordText = UIUtil.CreateText(clientGroup, "Password: ", 15, UIUtil.bodyFont)
	LayoutHelpers.Below(passwordText, accountNameText, 20)
	passwordText.Depth:Set(clientGroup.Depth() + 1)
	
	loginWindow.password = UIUtil.CreateCryptEditStd(clientGroup, 160, 20)
	LayoutHelpers.Below(loginWindow.password, loginWindow.accountName, 20)
	 
	loginWindow.rememberAccountChkBox = UIUtil.CreateCheckboxStd(clientGroup, '/dialogs/check-box_btn/radio')
	LayoutHelpers.Below(loginWindow.rememberAccountChkBox, passwordText, 30)
	loginWindow.rememberAccountChkBox.Depth:Set(clientGroup.Depth() + 2)
	loginWindow.rememberAccountChkBox:SetCheck(Prefs.GetFromCurrentProfile('remember_account') or false)
	
	loginWindow.rememberAccountChkBox.OnCheck = function(self, check)
		LOG('Remember account name set to', check)
		Prefs.SetToCurrentProfile('remember_account', check)
		if not check then
			Prefs.SetToCurrentProfile('account_name', '')
		end
	end
	
	local rememberAccountText = UIUtil.CreateText(clientGroup, "Remember Account Name", 14, UIUtil.bodyFont)
	LayoutHelpers.CenteredRightOf(rememberAccountText, loginWindow.rememberAccountChkBox, 5)
	rememberAccountText.Depth:Set(clientGroup.Depth() + 1)
	
	loginWindow.confirmButton = UIUtil.CreateButtonStd(clientGroup, '/buttons/gen', "Login", 16, -2, 0)
	loginWindow.confirmButton.Width:Set(60)
	loginWindow.confirmButton.Height:Set(30)
	LayoutHelpers.AtCenterIn(loginWindow.confirmButton, rememberAccountText, 2, 120)
	loginWindow.confirmButton.Depth:Set(clientGroup.Depth() + 2)
	loginWindow.confirmButton.OnClick = function()
		local accountName = loginWindow.accountName:GetText()
		local password = (''):join(loginWindow.password.cryptedText)
		
 		local LoginClient = Network.CreateLoginClient()
 		
 		local connectDialog = UIUtil.ShowInfoDialog(GUI.parent, "Connecting to Login Server...", "Abort", function() KillThread(LoginClient._timeoutThread) LoginClient:Destroy() end)
		LayoutHelpers.AtCenterIn(connectDialog, GUI.parent)
		
		local function modifyDialog(text, btnText)
			connectDialog.textLine[1]:SetText(text)
			if btnText then
				connectDialog._button1.label:SetText(btnText)
			end
		end
		
 		local OnConnectionFailed = function(client, obj, reason)
			modifyDialogt("Connection failed: " .. reason)
		end
		
		local OnConnectionToHostEstablished = function(client, obj, localId, localName, hostId)
			client:RequestLogin(accountName, password)
			modifyDialog("Validating login info...")
		end
		
		local OnDataReceived = function(client, obj, data)
			local _type = data._type
			local senderId = data.SenderID
			local senderName = data.SenderName
			
			if _type == "LoginValid" then
				local confirmationHash = data.login.confirmationHash
				modifyDialog("Login info validated, Launching game...")
				LOG("Confirmation hash:", confirmationHash)
				
		 		if loginWindow.rememberAccountChkBox:IsChecked() then
		 			Prefs.SetToCurrentProfile('account_name', loginWindow.accountName:GetText())
		 		end
		 		Prefs.CreateProfile(accountName)
				client:Destroy()
				# set confirmation hash into frontenddata
				SetFrontEndData('ConfirmationHash', confirmationHash)
				LaunchLobbySession()
			elseif _type == "LoginInvalid" then
				local reason = data.login.reason
				modifyDialog("Wrong username or password.", "Ok")
				
				client:Destroy()
			end
		end
		
		AddMethodHook(LoginClient, 'ConnectionToHostEstablished', OnConnectionToHostEstablished, LoginClient)
		AddMethodHook(LoginClient, 'ConnectionFailed', OnConnectionFailed, LoginClient)
		AddMethodHook(LoginClient, 'DataReceived', OnDataReceived, LoginClient)
		
 		# ProfilesExist and CreateProfile

		LOG('Account Name', accountName, 'Password', password)
	end
	return loginWindow
end

function CreateAnnounceWindow(parent)
    local announceWindow = Window(parent, 'Announcements:', nil, true, nil, nil, {true, true}, {true, true})
    announceWindow.Height:Set(257)
    announceWindow.Width:Set(300)
    LayoutHelpers.RightOf(announceWindow, GUI.loginWindow, 50)
    announceWindow.Depth:Set(parent.Depth() + 1)
    
    local clientGroup = announceWindow.ClientGroup
    
	announceWindow.list = ItemList(clientGroup)
	LayoutHelpers.FillParentFixedBorder(announceWindow.list, clientGroup, 10)
	LayoutHelpers.AtRightIn(announceWindow.list, clientGroup, 30)
	announceWindow.list:SetNewColors('White', 'Gray', 'Gray', 'Black', 'Black', 'Black')
	announceWindow.list:SetNewFont(UIUtil.bodyFont(), 16)
	UIUtil.CreateVertScrollbarFor(announceWindow.list)
	announceWindow.list.Depth:Set(clientGroup.Depth() + 2)
    announceWindow.list.announcements = {}
    
    local function ShowAnnouncement(index)
    	if GUI.announceDescription then
    		GUI.announceDescription:Destroy()
    	end
    	if not index then return end
    	local control = announceWindow.list
    	local announcement = control.announcements[index]
    	local title = announcement.title
    	local time = announcement.time
    	local descriptionStr = announcement.description
	    announceDescription = Window(parent, 'Posted at: ' .. time, nil, true, nil, nil, {true, true}, {true, true})
	    announceDescription.Height:Set(250)
	    announceDescription.Width:Set(300)
	    LayoutHelpers.Below(announceDescription, GUI.announceWindow, 20)
	    local clientGroup = announceDescription.ClientGroup
	    local description = MultiLineText(clientGroup, UIUtil.bodyFont(), 15, UIUtil.bodyColor())
	    LayoutHelpers.AtLeftTopIn(description, clientGroup, 20)
 	    description:SetText(descriptionStr)
  	    announceDescription.Height:Set(announceDescription._title.Height() + (description._numLines() + 1) * description.Height())
    	EffectHelpers.FadeIn(clientGroup, 0.5, 0, 1)
    	return announceDescription
  	end
    
	announceWindow.list.OnSelectionChanged = function(self, row)
		GUI.announceDescription = ShowAnnouncement(row)
	end
	return announceWindow
end

function CreateMiscWindow(parent)
    local miscwindow = Window(parent, 'Misc Window', nil, true, nil, nil, {true, true}, {true, true})
    miscwindow.Height:Set(250)
    miscwindow.Width:Set(300)
    LayoutHelpers.Below(miscwindow, GUI.loginWindow, 20)
    miscwindow.Depth:Set(parent.Depth() + 1)
    miscwindow.entries = {}
    local clientGroup = miscwindow.ClientGroup
    
    local function Register()
    	local darken = Bitmap(parent)
    	darken:SetSolidColor('Black')
    	darken.Depth:Set(parent.Depth() + 5)
    	LayoutHelpers.FillParent(darken, parent)
    	EffectHelpers.FadeIn(darken, 0.5, 0, 0.6)
    	
	    GUI.registerWindow = Window(parent, 'Register', nil, true, nil, nil, {true, true}, {true, true})
	    GUI.registerWindow.Height:Set(260)
	    GUI.registerWindow.Width:Set(320)
	    LayoutHelpers.AtCenterIn(GUI.registerWindow, parent)
	    GUI.registerWindow.Depth:Set(darken.Depth() + 1)
	    
	    local clientGroup = GUI.registerWindow.ClientGroup
	    
		local accountNameText = UIUtil.CreateText(clientGroup, "Account Name: ", 15, UIUtil.bodyFont)
		LayoutHelpers.AtLeftIn(accountNameText, clientGroup, 15)
		LayoutHelpers.AtTopIn(accountNameText, clientGroup, 15)
		accountNameText.Depth:Set(clientGroup.Depth() + 1)
	    
		GUI.registerWindow.accountName = UIUtil.CreateEditStd(clientGroup, 160, 20)
		LayoutHelpers.RightOf(GUI.registerWindow.accountName, accountNameText, 30)
	    
		local passwordText = UIUtil.CreateText(clientGroup, "Password: ", 15, UIUtil.bodyFont)
		LayoutHelpers.Below(passwordText, accountNameText, 20)
		passwordText.Depth:Set(clientGroup.Depth() + 1)
		
		GUI.registerWindow.password = UIUtil.CreateCryptEditStd(clientGroup, 160, 20)
		LayoutHelpers.Below(GUI.registerWindow.password, GUI.registerWindow.accountName, 20)
		
		local copasswordText = UIUtil.CreateText(clientGroup, "Confirm Password: ", 15, UIUtil.bodyFont)
		LayoutHelpers.Below(copasswordText, passwordText, 20)
		copasswordText.Depth:Set(clientGroup.Depth() + 1)
		
		GUI.registerWindow.copassword = UIUtil.CreateCryptEditStd(clientGroup, 160, 20)
		LayoutHelpers.Below(GUI.registerWindow.copassword, GUI.registerWindow.password, 20)
		
		local emailText = UIUtil.CreateText(clientGroup, "Email: ", 15, UIUtil.bodyFont)
		LayoutHelpers.Below(emailText, copasswordText, 20)
		emailText.Depth:Set(clientGroup.Depth() + 1)
	    
		GUI.registerWindow.email = UIUtil.CreateEditStd(clientGroup, 160, 20)
		LayoutHelpers.Below(GUI.registerWindow.email, GUI.registerWindow.copassword, 20)
		
        GUI.registerWindow.confirm = UIUtil.CreateButtonStd( clientGroup, '/buttons/gen', 'Confirm', 18, -2)
        GUI.registerWindow.confirm.Width:Set(70)
		GUI.registerWindow.confirm.Height:Set(40)
		LayoutHelpers.AtBottomIn(GUI.registerWindow.confirm, clientGroup, 10)
		LayoutHelpers.AtRightIn(GUI.registerWindow.confirm, clientGroup, 10)
		GUI.registerWindow.confirm.Depth:Set(clientGroup.Depth() + 2)
		GUI.registerWindow.confirm.OnClick = function(self)
	 		local accountName = GUI.registerWindow.accountName:GetText()
	 		local pw1 = (''):join(GUI.registerWindow.password.cryptedText)
	 		local pw2 = (''):join(GUI.registerWindow.copassword.cryptedText)
	 		local email = GUI.registerWindow.email:GetText()
	 		
	 		local accountNameValid = (accountName:len() != 0 and accountName:len() < 16 and accountName:isascii())
	 		local passwordValid = (pw1:len() != 0 and pw1:len() < 12 and pw1:isascii())
	 		local emailValid = (email:len() != 0 and email:isemail())
	 		
	 		local function showError(text, btnText)
	 			local dialog = UIUtil.ShowInfoDialog(darken, text, btnText, function() end)
	 			LayoutHelpers.AtCenterIn(dialog, parent)
	 			return dialog
	 		end
	 		
	 		if not accountNameValid or not passwordValid then
	 			showError('Invalid account name / password', 'Ok')
	 			return
	 		elseif pw1 != pw2 then
	 			showError('Passwords do not match.', 'Ok')
	 			return
	 		elseif not emailValid then
	 			showError('Enter a valid email, name@server.suffix', 'Ok')
	 			return
	 		end
	 		LOG('Account Name', accountName, 'Password', pw1, 'Email', email)
	 		
	 		local LoginClient = Network.CreateLoginClient()
	 		
	 		local connectDialog = UIUtil.ShowInfoDialog(GUI.parent, "Connecting to Login Server...", "Abort", 
		 		function() 
		 			KillThread(LoginClient._timeoutThread) 
		 			LoginClient:Destroy() 
		 			GUI.registerWindow:Destroy()
					darken:Destroy()
		 		end)
			LayoutHelpers.AtCenterIn(connectDialog, GUI.parent)
			
			local function modifyDialog(text, btnText)
				connectDialog.textLine[1]:SetText(text)
				if btnText then
					connectDialog._button1.label:SetText(btnText)
				end
			end
			
	 		local OnConnectionFailed = function(client, obj, reason)
				modifyDialog("Connection failed: " .. reason, "Ok")
			end
			
			local OnConnectionToHostEstablished = function(client, obj, localId, localName, hostId)
				client:RequestRegistration(accountName, pw1, email)
				modifyDialog("Validating registration info...")
			end
			
			local OnDataReceived = function(client, obj, data)
				local _type = data._type
				local senderId = data.SenderID
				local senderName = data.SenderName
				
				if _type == "RegistrationValid" then
					local accountName = data.registration.accountName
			 		modifyDialog("Registration completed, you can now log on.", "Ok")
			 		GUI.loginWindow.accountName:SetText(accountName)
			 		GUI.loginWindow.password:SetCryptedText('')
			 		Prefs.CreateProfile(accountName)
					client:Destroy()
				elseif _type == "RegistrationInvalid" then
					local reason = data.registration.reason
					modifyDialog("Registration failed, reason: " .. reason, "Ok")
					
					client:Destroy()
				end
			end
			
			AddMethodHook(LoginClient, 'ConnectionToHostEstablished', OnConnectionToHostEstablished, LoginClient)
			AddMethodHook(LoginClient, 'ConnectionFailed', OnConnectionFailed, LoginClient)
			AddMethodHook(LoginClient, 'DataReceived', OnDataReceived, LoginClient)
			# CreateProfile
		end
		
        GUI.registerWindow.cancel = UIUtil.CreateButtonStd( clientGroup, '/buttons/gen', 'Cancel', 18, -2)
        GUI.registerWindow.cancel.Width:Set(70)
		GUI.registerWindow.cancel.Height:Set(40)
		LayoutHelpers.AtBottomIn(GUI.registerWindow.cancel, clientGroup, 10)
		LayoutHelpers.AtLeftIn(GUI.registerWindow.cancel, clientGroup, 10)
		GUI.registerWindow.cancel.Depth:Set(clientGroup.Depth() + 2)
		GUI.registerWindow.cancel.OnClick = function(self)
			GUI.registerWindow:Destroy()
			darken:Destroy()
		end
    end
    
    local register = UIUtil.CreateLink(clientGroup, "- Create an Account ", 15, UIUtil.bodyFont, UIUtil.fontColor, UIUtil.bodyColor, Register, true)
    LayoutHelpers.AtLeftTopIn(register, clientGroup, 20)
    register.Depth:Set(clientGroup.Depth() + 2)
    table.insert(miscwindow.entries, register)
    
    miscwindow.Height:Set(function()
    	local height = 0
    	for i, entry in miscwindow.entries do
    		height = entry.Height() + height
    	end
    	return miscwindow._title.Height() + 20 + height
    end)
    return miscwindow
end